/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package Escalonador;

import Programas.CriarDiversosProcesso;
import Programas.Processo;
import cpu.Cpu;
import gerenciador_de_memoria.GerenciadorMemoria;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author Mateus
 */
public class Escalonador extends Thread {

    Cpu cpu = new Cpu(this);
    int auxP = 0;
    Processo processo;
    public Map<Integer, Processo> ListaProcesso;
    private CriarDiversosProcesso criar;
    private ArrayList<ArrayList<Processo>> todosProcessos;
    private GerenciadorMemoria gm;
    ArrayList<Processo> armazenaProcessos = new ArrayList<Processo>();
    Random x = new Random();
    private String politica;

    public String getPolitica() {
        return politica;
    }

    public void setPolitica(String politica) {
        this.politica = politica;
    }
    int bt, at, process, pr, pri, tat, wt, copy_of_bt;
    int p1 = 0, p2 = 0;

    /**
     *
     * @param criar:Pega os processo craidos na classe CriaDIversos Processos
     * @param todosProcessos: Contem um array de arrya de todos os processos
     * criados
     * @param gm: EnviaÂ´os processos escalonados para o gerenciador de memoria
     */
    public Escalonador(CriarDiversosProcesso criar, ArrayList<ArrayList<Processo>> todosProcessos, GerenciadorMemoria gm) {
        this.criar = criar;
        this.todosProcessos = todosProcessos;
        this.gm = gm;
        politica = "Prioridade";
        ListaProcesso = new HashMap<Integer, Processo>();
        processo = new Processo();

    }

    private Escalonador() {
    }

    /**
     *
     * Politica de escalonamento RoundRobin,cada processo recebe um quanto
     * determinado em 35, ao final desse tempo, o processo Ã© suspenso e outro
     * processo Ã© colocado em execuÃ§Ã£o. Define o estado do processo como
     * executando,envia os processos escalonados para o gerenciado de memoria,e
     * para a cpu que retorna o novo estado do processo, se estiver bloqueado,
     * roda na CPU Novamente
     *
     */
    public void RoundRobin() throws InterruptedException {
        // cria();
        // quantidade de processo
        int n = p2;
        int tq = 35;
        int sum = 0;
        int p = 0;

        Escalonador op[] = new Escalonador[n + 1];

        for (int i = 0; i <= n; i++) {
            op[i] = new Escalonador();
        }

        for (int m = 1; m <= n; m++) {
            op[m].pr = m;
            op[m].bt = todosProcessos.get(p1 - 1).get(p).getTempoDeExecucao();
            p++;
            sum += op[m].bt;
            op[m].copy_of_bt = op[m].bt;

        }
        int t = 0;
        while (t < sum) {
            for (int j = 1; j <= n; j++) {
                if (op[j].bt > 0) {
                    if (op[j].bt > tq) {
                        op[j].bt -= tq;
                        t += tq;
                    } else {
                        t += op[j].bt;
                        op[j].bt = 0;
                        op[j].tat = t;
                        op[j].wt = t - op[j].copy_of_bt;
                    }
                }
            }
        }

        int tot_turn = 0, tot_wait = 0;
        for (int i = 1; i <= n; i++) {
            tot_turn += op[i].tat;
            tot_wait += op[i].wt;
        }

        float avg_turn = (float) tot_turn / n;
        float avg_wait = (float) tot_wait / n;

        for (int m = 1; m <= n; m++) {
            // p-1 porque o cont comeÃ§a do 1, p1 Ã© o numero do lote
            //todosProcessos.get(p1 - 1).get((op[m].pr) - 1).setEstado("executando");
            Random r = new Random();
            auxP = r.nextInt(2);
            if (auxP == 0) {
                processo.mudarEstado("bloqueado");
            } else if (auxP == 1) {
                processo.mudarEstado("pronto");
            }
            gm.alocaProcesso(todosProcessos.get(p1 - 1).get((op[m].pr) - 1), this);
            cpu.addProcesso(todosProcessos.get(p1 - 1).get((op[m].pr) - 1));

        }
        // Se tiver pronto vai para a memoria se estiver bloqueado volta para
        // cpu
        do {
            ArrayList<Processo> armazenaProcessos2 = armazenaProcessos;
            for (int i = 0; i < armazenaProcessos2.size(); i++) {
                if (armazenaProcessos2.get(i).getEstado().equals("pronto")) {
                    gm.alocaProcesso(armazenaProcessos2.get(i), this);
                } else {
                    tempo();
                    cpu.addProcesso(armazenaProcessos2.get(i));


                }
            }

            armazenaProcessos.removeAll(armazenaProcessos2);
        } while (!armazenaProcessos.isEmpty());
    }

    /**
     * Os processo com maior prioridade sÃ£o executados primeiros, para nÃ£o ser
     * executado infinitamente, sua prioridade vai diminuido Define o estado do
     * processo como executando,envia os processos escalonados para o gerenciado
     * de memoria,e para a cpu que retorna o novo estado do processo, se estiver
     * bloqueado, roda na CPU Novamente
     *
     */
    public void Prioridade() throws InterruptedException {

        // cria();
        int n = p2;
        int process[] = new int[n + 1];
        int burst[] = new int[n + 1];
        int priority[] = new int[n + 1];
        int waiting[] = new int[n + 1];
        int turn[] = new int[n + 1];
        int p = 0;
        for (int m = 1; m <= n; m++) {
            process[m] = m;
        }

        for (int m = 1; m <= n; m++) {

            burst[m] = todosProcessos.get(p1 - 1).get(p).getTempoDeExecucao();
            priority[m] = todosProcessos.get(p1 - 1).get(p).getPrioridade();
            p++;
        }
        int temp;
        for (int i = 1; i < n; i++) {
            for (int j = 1; j < n; j++) {
                if (priority[j + 1] < priority[j]) {
                    temp = priority[j + 1];
                    priority[j + 1] = priority[j];
                    priority[j] = temp;
                    temp = burst[j + 1];
                    burst[j + 1] = burst[j];
                    burst[j] = temp;
                    temp = process[j + 1];
                    process[j + 1] = process[j];
                    process[j] = temp;

                }
            }
        }


        for (int i = 2; i <= n; i++) {
            turn[i] = burst[i] + turn[i - 1];
            waiting[i] = turn[i] - burst[i];

        }

        int tot_turn = 0, tot_wait = 0;
        for (int i = 1; i <= n; i++) {

            tot_turn += turn[i];
            tot_wait += waiting[i];

        }
        float avg_turn = (float) tot_turn / n;
        float avg_wait = (float) tot_wait / n;

        for (int m = 1; m <= n; m++) {
            // todosProcessos.get(p1 - 1).get((process[m]) - 1).setEstado("executando");
            Random r = new Random();
            auxP = r.nextInt(2);
            if (auxP == 0) {
                processo.mudarEstado("bloqueado");
            } else if (auxP == 1) {
                processo.mudarEstado("pronto");
            }
            gm.alocaProcesso(todosProcessos.get(p1 - 1).get((process[m]) - 1), this);
            cpu.addProcesso(todosProcessos.get(p1 - 1).get((process[m]) - 1));
            try {
                Thread.sleep(4000);
            } catch (InterruptedException ex) {
                Logger.getLogger(Escalonador.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
        do {
            ArrayList<Processo> armazenaProcessos2 = armazenaProcessos;
            for (int i = 0; i < armazenaProcessos2.size(); i++) {
                if (armazenaProcessos2.get(i).getEstado().equals("pronto")) {
                    gm.alocaProcesso(armazenaProcessos2.get(i), this);

                } else {
                    tempo();
                    cpu.addProcesso(armazenaProcessos2.get(i));

                }
            }
            armazenaProcessos.removeAll(armazenaProcessos2);
        } while (!armazenaProcessos.isEmpty());

    }

    /**
     * Realiza uma estimativa com base no comportamento passado e executa o
     * processo cujo tempo de execuÃ§Ã£o estimado seja o menor * Define o estado
     * do processo como executando,envia os processos escalonados para o
     * gerenciado de memoria,e para a cpu que retorna o novo estado do processo,
     * se estiver bloqueado, roda na CPU Novamente
     */
    public void ProximoMaisCurto() {
        int n = p2;
        int p = 0;
        int process[] = new int[n + 1];
        int burst[] = new int[n + 1];
        int waiting[] = new int[n + 1];
        int turn[] = new int[n + 1];

        for (int m = 1; m <= n; m++) {
            process[m] = m;
            burst[m] = todosProcessos.get(p1 - 1).get(p).getTempoDeExecucao();
            p++;
        }
        int temp;
        for (int i = 1; i < n; i++) {
            for (int j = 1; j < n; j++) {
                if (burst[j + 1] < burst[j]) {
                    temp = burst[j + 1];
                    burst[j + 1] = burst[j];
                    burst[j] = temp;
                    temp = process[j + 1];
                    process[j + 1] = process[j];
                    process[j] = temp;
                }

            }

        }


        for (int i = 2; i <= n; i++) {
            turn[i] = burst[i] + turn[i - 1];
            waiting[i] = turn[i] - burst[i];
        }

        int tot_turn = 0, tot_wait = 0;
        for (int i = 1; i <= n; i++) {
            tot_turn += turn[i];
            tot_wait += waiting[i];
        }

        float avg_turn = (float) tot_turn / n;
        float avg_wait = (float) tot_wait / n;


        for (int m = 1; m <= n; m++) {
            // todosProcessos.get(p1 - 1).get((process[m]) - 1).setEstado("executando");
            Random r = new Random();
            auxP = r.nextInt(2);
            if (auxP == 0) {
                processo.mudarEstado("bloqueado");
            } else if (auxP == 1) {
                processo.mudarEstado("pronto");
            }
            gm.alocaProcesso(todosProcessos.get(p1 - 1).get((process[m]) - 1), this);
            cpu.addProcesso(todosProcessos.get(p1 - 1).get((process[m]) - 1));
        }
        do {

            ArrayList<Processo> armazenaProcessos2 = armazenaProcessos;
            for (int i = 0; i < armazenaProcessos2.size(); i++) {
                if (armazenaProcessos2.get(i).getEstado().equals("pronto")) {
                    gm.alocaProcesso(armazenaProcessos2.get(i), this);

                } else {
                    tempo();
                    cpu.addProcesso(armazenaProcessos2.get(i));

                }
            }
            armazenaProcessos.removeAll(armazenaProcessos2);
        } while (!armazenaProcessos.isEmpty());


    }

    /**
     * Cada processo recebe â€œticketsâ€� que lhe dÃ£o direito de execuÃ§Ã£o A cada
     * troca de processo um â€œticketsâ€� Ã© sorteado O dono do â€œticketsâ€� sorteado
     * recebe o direito de ocupar a CPU Define o estado do processo como
     * executando,envia os processos escalonados para o gerenciado de memoria,e
     * para a cpu que retorna o novo estado do processo, se estiver bloqueado,
     * roda na CPU Novamente
     */
    public void Loteria() {
        Random rand = new Random();
        int n = p2;
        int pp = 0;
        int process[] = new int[n + 1];
        int arrival[] = new int[n + 1];

        for (int m = 1; m <= n; m++) {
            process[m] = todosProcessos.get(p1 - 1).get(pp).getIdProcesso();
            arrival[m] = todosProcessos.get(p1 - 1).get(pp).getTempoDeChegada();
            pp++;
        }
        boolean insert = false;
        int p = 0;
        int q = 0;
        int max_tick = 0;
        int ticket[][] = new int[10][10];
        int temp[] = new int[10];

        for (int z = 1; z < n; z++) {
            // gera o ticket
            int digit = (int) rand.nextInt(30);
            arrival[z] = digit;
        }


        for (int i = 1; i < n; i++) {
            for (int j = 1; j < n - 1; j++) {
                if (arrival[j] >= arrival[j + 1]) {
                    int temp2 = arrival[j];
                    arrival[j] = arrival[j + 1];
                    arrival[j + 1] = temp2;

                    int temp1 = process[j];
                    process[j] = process[j + 1];
                    process[j + 1] = temp1;
                }
            }
        }
        int time = arrival[0];

        int[] request = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
        int[] lottery = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
        for (int z = 1; z < n; z++) {

            int digit = (int) rand.nextInt(20);

            if (digit == 0) {
                digit = 5;
            }

            request[z] = digit;

        }

        int aux = 0;
        for (int i = 1; i <= n; i++) {
            // System.out.print("\t process1 : " + process[i]); 
            todosProcessos.get(p1 - 1).get(aux).setIdProcesso(process[i]);
            // todosProcessos.get(p1 - 1).get(aux).setEstado("executando"); 
            Random r = new Random();
            auxP = r.nextInt(2);
            if (auxP == 0) {
                processo.mudarEstado("bloqueado");
            } else if (auxP == 1) {
                processo.mudarEstado("pronto");
            }
            gm.alocaProcesso(todosProcessos.get(p1 - 1).get(aux), this);   
            
            cpu.addProcesso(todosProcessos.get(p1 - 1).get(aux));
            aux++;
        }
        do {
            ArrayList<Processo> armazenaProcessos2 = armazenaProcessos;
            for (int i = 0; i < armazenaProcessos2.size(); i++) {
                if (armazenaProcessos2.get(i).getEstado().equals("pronto")) {
                    gm.alocaProcesso(armazenaProcessos2.get(i), this);

                } else {
                    tempo();
                    cpu.addProcesso(armazenaProcessos2.get(i));
                }
            }
            armazenaProcessos.removeAll(armazenaProcessos2);
        } while (!armazenaProcessos.isEmpty());



    }

    /**
     * Como os processos nÃ£o tem dono, e nem ganham uma fraÃ§Ã£o justa da cpu, o
     * escalonamento Ã© feito comparando as prioridades dos processos Define o
     * estado do processo como executando,envia os processos escalonados para o
     * gerenciado de memoria,e para a cpu que retorna o novo estado do processo,
     * se estiver bloqueado, roda na CPU Novamente
     */
    public void FairShare() {
        int n = p2;
        int pp = 0;
        int process2[] = new int[p2 + 1];
        for (int m = 1; m <= n; m++) {
            process2[m] = todosProcessos.get(p1 - 1).get(pp).getIdProcesso();
            pp++;
        }
        int totalTime = 0;
        int flag = 0;
        for (int i = 1; i < n; i++) {
            if (todosProcessos.get(p1 - 1).get(flag).getPrioridade() > todosProcessos.get(p1 - 1).get(flag + 1).getPrioridade()) {
                todosProcessos.get(p1 - 1).get(flag).setIdProcesso(process2[i]);
                //   todosProcessos.get(p1 - 1).get(flag).setEstado("executando");    
                totalTime += todosProcessos.get(p1 - 1).get(flag).getTempoDeExecucao();
                Random r = new Random();
                auxP = r.nextInt(2);
                if (auxP == 0) {
                    processo.mudarEstado("bloqueado");
                } else if (auxP == 1) {
                    processo.mudarEstado("pronto");
                }
                gm.alocaProcesso(todosProcessos.get(p1 - 1).get(flag), this);
                cpu.addProcesso(todosProcessos.get(p1 - 1).get(flag));
                flag++;
            }
        }
        do {
            ArrayList<Processo> armazenaProcessos2 = armazenaProcessos;
            for (int i = 0; i < armazenaProcessos2.size(); i++) {
                if (armazenaProcessos2.get(i).getEstado().equals("pronto")) {
                    gm.alocaProcesso(armazenaProcessos2.get(i), this);

                } else {
                    tempo();
                    cpu.addProcesso(armazenaProcessos2.get(i));
                }
            }
            armazenaProcessos.removeAll(armazenaProcessos2);
        } while (!armazenaProcessos.isEmpty());
    }

    /**
     * Cada processo possui um quantum, Assim, a cada vez que um processo Ã©
     * executado e suspenso ele recebe mais tempo para execuÃ§Ã£o mas passa para
     * uma fila com menor prioridade de execuÃ§Ã£o. Define o estado do processo
     * como executando,envia os processos escalonados para o gerenciado de
     * memoria,e para a cpu que retorna o novo estado do processo, se estiver
     * bloqueado, roda na CPU Novamente
     */
    public void FilasMultiplas() {

        int n = p2;
        int p = 0;
        Escalonador op[] = new Escalonador[n + 1];
        for (int i = 0; i <= n; i++) {
            op[i] = new Escalonador();
        }
        int sum = 0;
        for (int m = 1; m <= n; m++) {
            op[m].process = m;
            op[m].bt = todosProcessos.get(p1 - 1).get(p).getTempoDeExecucao();
            op[m].at = todosProcessos.get(p1 - 1).get(p).getTempoDeChegada();
            op[m].pri = todosProcessos.get(p1 - 1).get(p).getPrioridade();
            sum += op[m].bt;
            op[m].copy_of_bt = op[m].bt;
            p++;

        }
        for (int t = 1; t <= sum; t++) {

            int min = 9999, index = 0;
            for (int j = 1; j <= n; j++) {
                if (op[j].at < t && op[j].pri < min && op[j].bt > 0) {
                    min = op[j].pri;
                    index = j;
                }
            }

            op[index].bt -= 1;
            if (op[index].bt == 0) {
                op[index].tat = t - op[index].at;
                op[index].wt = op[index].tat - op[index].copy_of_bt;
            }
        }

        int tot_turn = 0, tot_wait = 0;
        for (int i = 1; i <= n; i++) {
            tot_turn += op[i].tat;
            tot_wait += op[i].wt;
        }
        float avg_turn = (float) tot_turn / n;
        float avg_wait = (float) tot_wait / n;
        for (int m = 1; m <= n; m++) {
            // todosProcessos.get(p1 - 1).get((op[m].process) - 1).setEstado("executando");
            Random r = new Random();
            auxP = r.nextInt(2);
            if (auxP == 0) {
                processo.mudarEstado("bloqueado");
            } else if (auxP == 1) {
                processo.mudarEstado("pronto");
            }
            gm.alocaProcesso(todosProcessos.get(p1 - 1).get((op[m].process) - 1), this);
            cpu.addProcesso(todosProcessos.get(p1 - 1).get((op[m].process) - 1));

        }
        do {
            ArrayList<Processo> armazenaProcessos2 = armazenaProcessos;
            for (int i = 0; i < armazenaProcessos2.size(); i++) {
                if (armazenaProcessos2.get(i).getEstado().equals("pronto")) {
                    gm.alocaProcesso(armazenaProcessos2.get(i), this);

                } else {
                    tempo();
                    cpu.addProcesso(armazenaProcessos2.get(i));
                }
            }
            armazenaProcessos.removeAll(armazenaProcessos2);
        } while (!armazenaProcessos.isEmpty());

    }

    /**
     *
     * @param p : Processo passaod pela cpu. armazena os processos retornados da
     * cpu e insere na lista de armazenamento de processo se o processo dessa
     * lista estiver como bloqueado volata para a cpu
     */
    public void retornaProcesso(Processo p) {
        armazenaProcessos.add(p);

    }

    /**
     * Thread que executa o escalonamento de processo, estarta a cpu, seta a
     * classe processo para rodar p1 recebe o lote de processos p2 recebe a
     * lista com todos os processo while true executa o metodo de escalonamento
     * definido, atÃ© a thread parar de rodar ou a politica mudar
     *
     */
    public void run() {
        cpu.start();
        criar.pause = false;
        p1 = todosProcessos.size();
        while (p1 <= 0) {
            p1 = todosProcessos.size();
            try {
                Thread.sleep(70);
            } catch (InterruptedException ex) {
                Logger.getLogger(Escalonador.class.getName()).log(Level.SEVERE, null, ex);
            }
        }

        while (true) {
            p1 = todosProcessos.size();
            p2 = todosProcessos.get(p1 - 1).size();
            try {
                Thread.sleep(9000);
            } catch (InterruptedException ex) {
                Logger.getLogger(Escalonador.class.getName()).log(Level.SEVERE, null, ex);
            }
            try {
                switch (politica) {
                    case "RoundRobin":
                        RoundRobin();
                        break;
                    case "Prioridade":
                        Prioridade();
                        break;
                    case "ProximoMaisCurto":
                        ProximoMaisCurto();
                        break;
                    case "Loteria":
                        Loteria();
                        break;
                    case "FairShare":
                        FairShare();
                        break;
                    case "FilasMultiplas":
                        FilasMultiplas();
                        break;
                }
            } catch (InterruptedException ex) {
                Logger.getLogger(Escalonador.class.getName()).log(Level.SEVERE, null, ex);
            }

        }



    }

    /**
     * MÃ©todo para manipulaÃ§Ã£o das diferentes polÃ­ticas do sistema operacional
     * Para a classe processo de gerar processo, inicializa novamente, varialve
     * poli recebe a nova politica
     *
     * @param poli Sinal para troca de polÃ­tica de escalonamento
     * @throws InterruptedException
     */
    public void schellPolicy(String poli) throws InterruptedException {

        criar.pause = true;
        criar.pause = false;
        politica = poli;
    }

    /**
     * MÃ©todo para parar processos em execuÃ§Ã£o
     *
     * @param pId = Identificador do processo que serÃ¡ bloqueado
     * @return Caso necessÃ¡rio, retornar erro
     */
    public void waitProcess(int id) {
        if (ListaProcesso.containsKey(id)) {
            ListaProcesso.get(id).mudarEstado("bloqueado");
            ListaProcesso.get(id).setModificado(true);
            Processo p = ListaProcesso.get(id);
            // gm.alocaProcesso(p, this);
        }
    }

    /**
     *
     * @param pId Identificador do processo que estÃ¡ bloqueado e serÃ¡ enviado
     * para a fila de prontos
     * @return Caso necessÃ¡rio, retornar erro
     */
    public void resumeProcess(int id) {
        if (ListaProcesso.containsKey(id)) {
            ListaProcesso.get(id).mudarEstado("pronto");
            ListaProcesso.get(id).setModificado(true);
            Processo p = ListaProcesso.get(id);
            // gm.alocaProcesso(p, this);
        }
    }
    
    
 public String schell2() {
        Iterator<Map.Entry<Integer, Processo>> it = ListaProcesso.entrySet().iterator();
        String lista = "ID\tGRUPO ID\tESTADOs\n";
        while (it.hasNext()) {
            Map.Entry<Integer, Processo> entrada = it.next();
            Processo p = entrada.getValue();
            lista += p.getIdProcesso() + "\t" + p.getGroupId() + "\t" + p.getEstado() + "\n";
        }
        return lista;
    }

    public void tempo() {
        try {//Cria um lote de processos a cada 1000 milissegundos
            sleep((long) 2500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
